﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace RestoreHandler
{
    public class IECache
    {
        #region 引进所需要的Dll
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct INTERNET_CACHE_ENTRY_INFO
        {
            public int dwStructSize;
            public IntPtr lpszSourceUrlName;
            public IntPtr lpszLocalFileName;
            public int CacheEntryType;
            public int dwUseCount;
            public int dwHitRate;
            public int dwSizeLow;
            public int dwSizeHigh;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastModifiedTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME ExpireTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastSyncTime;
            public IntPtr lpHeaderInfo;
            public int dwHeaderInfoSize;
            public IntPtr lpszFileExtension;
            public int dwExemptDelta;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct SYSTEMTIME
        {
            public short wYear;
            public short wMonth;
            public short wDayOfWeek;
            public short wDay;
            public short wHour;
            public short wMinute;
            public short wSecond;
            public short wMilliseconds;
        }

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern int FileTimeToSystemTime(
            IntPtr lpFileTime,
            IntPtr lpSystemTime);

        [DllImport("wininet", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "FindFirstUrlCacheGroup", CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr FindFirstUrlCacheGroup(
            int dwFlags,
            int dwFilter,
            IntPtr lpSearchCondition,
            int dwSearchCondition,
            ref long lpGroupId,
            IntPtr lpReserved);

        [DllImport("wininet",
            SetLastError = true,
            CharSet = CharSet.Auto,
            EntryPoint = "FindNextUrlCacheGroup",
            CallingConvention = CallingConvention.StdCall)
        ]
        private static extern bool FindNextUrlCacheGroup( IntPtr hFind, ref long lpGroupId, IntPtr lpReserved);

        [DllImport("wininet", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "DeleteUrlCacheGroup", CallingConvention = CallingConvention.StdCall)
        ]
        private static extern bool DeleteUrlCacheGroup(
            long GroupId,
            int dwFlags,
            IntPtr lpReserved);


        [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Auto) ]
        private static extern IntPtr FindFirstUrlCacheEntry(
            [MarshalAs(UnmanagedType.LPTStr)] string UrlSearchPattern,
            IntPtr lpFirstCacheEntryInfo,
            ref int lpdwFirstCacheEntryInfoBufferSize);

        [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool FindNextUrlCacheEntry(
            IntPtr hEnumHandle,
            IntPtr lpNextCacheEntryInfo,
            ref int lpdwNextCacheEntryInfoBufferSize);

        [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern bool GetUrlCacheEntryInfo(
            [MarshalAs(UnmanagedType.LPTStr)] string lpszUrlName,
            IntPtr lpCacheEntryInfo,
            ref int lpdwCacheEntryInfoBufferSize
            );

        [DllImport("wininet.dll")]
        private static extern bool FindCloseUrlCache(IntPtr hEnumHandle);

        [DllImport("wininet", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "DeleteUrlCacheEntryA", CallingConvention = CallingConvention.StdCall) ]
        private static extern bool DeleteUrlCacheEntry(
            IntPtr lpszUrlName);

        const int ERROR_NO_MORE_ITEMS = 259;
        /// 
        /// Retrieves the flags, type, and disk quota attributes of the cache group. This is used by the GetUrlCacheGroupAttribute function. 
        /// 
        const int CACHEGROUP_ATTRIBUTE_BASIC = 0x00000001;
        /// 
        /// Sets or retrieves the flags associated with the cache group. This is used by the GetUrlCacheGroupAttribute and SetUrlCacheGroupAttribute functions. 
        /// 
        const int CACHEGROUP_ATTRIBUTE_FLAG = 0x00000002;
        /// 
        /// Retrieves all the attributes of the cache group. This is used by the GetUrlCacheGroupAttribute function. 
        /// 
        const uint CACHEGROUP_ATTRIBUTE_GET_ALL = 0xffffffff;
        /// 
        /// Sets or retrieves the group name of the cache group. This is used by the GetUrlCacheGroupAttribute and SetUrlCacheGroupAttribute functions. 
        /// 
        const int CACHEGROUP_ATTRIBUTE_GROUPNAME = 0x000000010;
        /// 
        /// Sets or retrieves the disk quota associated with the cache group. This is used by the GetUrlCacheGroupAttribute and SetUrlCacheGroupAttribute functions. 
        /// 
        const int CACHEGROUP_ATTRIBUTE_QUOTA = 0x00000008;
        /// 
        /// Sets or retrieves the group owner storage associated with the cache group. This is used by the GetUrlCacheGroupAttribute and SetUrlCacheGroupAttribute functions. 
        /// 
        const int CACHEGROUP_ATTRIBUTE_STORAGE = 0x00000020;
        /// 
        /// Sets or retrieves the cache group type. This is used by the GetUrlCacheGroupAttribute and SetUrlCacheGroupAttribute functions. 
        /// 
        const int CACHEGROUP_ATTRIBUTE_TYPE = 0x00000004;
        /// 
        /// Indicates that all the cache entries associated with the cache group should be deleted, unless the entry belongs to another cache group. 
        /// 
        const int CACHEGROUP_FLAG_FLUSHURL_ONDELETE = 0x00000002;
        /// 
        /// Indicates that the function should only create a unique GROUPID for the cache group and not create the actual group. 
        /// 
        const int CACHEGROUP_FLAG_GIDONLY = 0x00000004;
        /// 
        /// Indicates that the cache group cannot be purged. 
        /// 
        const int CACHEGROUP_FLAG_NONPURGEABLE = 0x00000001;
        /// 
        /// Sets the type, disk quota, group name, and owner storage attributes of the cache group. This is used by the SetUrlCacheGroupAttribute function. 
        /// 
        const int CACHEGROUP_READWRITE_MASK = 0x0000003c;
        /// 
        /// Indicates that all of the cache groups in the user''s system should be enumerated. 
        /// 
        const int CACHEGROUP_SEARCH_ALL = 0x00000000;
        /// 
        /// Not currently implemented. 
        /// 
        const int CACHEGROUP_SEARCH_BYURL = 0x00000001;
        /// 
        /// Indicates that the cache group type is invalid. 
        /// 
        const int CACHEGROUP_TYPE_INVALID = 0x00000001;
        /// 
        /// Length of the group owner storage array. 
        /// 
        const int GROUP_OWNER_STORAGE_SIZE = 0x00000004;
        /// 
        /// Maximum number of characters allowed for a cache group name.
        /// 
        const int GROUPNAME_MAX_LENGTH = 0x00000078;
        #endregion

        #region FileTimeToSystemTime
        private DateTime FILETIMEtoDataTime(System.Runtime.InteropServices.ComTypes.FILETIME time)
        {
            IntPtr filetime = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME)));
            IntPtr systime = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SYSTEMTIME)));
            Marshal.StructureToPtr(time, filetime, true);
            FileTimeToSystemTime(filetime, systime);
            SYSTEMTIME systemTime = (SYSTEMTIME)Marshal.PtrToStructure(systime, typeof(SYSTEMTIME));
            DateTime dateTime = new DateTime(systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond);
            return dateTime;
        }
        #endregion

        #region 加载数据
        /// 
        /// 获得指定的URL的cache文件名
        /// 
        /// URL
        /// 假如不存在该cache，返回空字符串
        public string GetURLCacheFileName(string URL)
        {
            string s = "";
            int needed = 0, bufSize;
            IntPtr buf;
            INTERNET_CACHE_ENTRY_INFO cacheItem;

            FindFirstUrlCacheEntry(null, IntPtr.Zero, ref needed);

            if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
                return s;

            bufSize = needed;
            buf = Marshal.AllocHGlobal(bufSize);
            GetUrlCacheEntryInfo(URL, buf, ref needed);
            try
            {
                cacheItem = (INTERNET_CACHE_ENTRY_INFO)Marshal.PtrToStructure(buf, typeof(INTERNET_CACHE_ENTRY_INFO));
                /*
                 * 以下这一段儿写的有些弱智。。。。但是没有措施。。。。
                 *不知道为什么，通过上面的方法取出来的文件的Cache路径，
                 * 有时候是乱码，有时候用PtrToStringAnsi可是又正常。。。。。
                 * 唉，所以只能用这个笨措施。。。。
                */
                s = GetName(cacheItem.lpszLocalFileName);
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message + ex.StackTrace);
            }
            Marshal.FreeHGlobal(buf);
            return s;
        }

        private String GetName(IntPtr lpszLocalFileName)
        {
            String s = Marshal.PtrToStringAuto(lpszLocalFileName);
            if (s != null)
            {
                if (s.IndexOf("Temporary Internet Files") == -1)
                    s = Marshal.PtrToStringAnsi(lpszLocalFileName);
                if (s.IndexOf("Temporary Internet Files") == -1)
                    s = Marshal.PtrToStringBSTR(lpszLocalFileName);
                if (s.IndexOf("Temporary Internet Files") == -1)
                    s = Marshal.PtrToStringUni(lpszLocalFileName);
            }
            return s;
        }

        public void GetCacheFile()
        {
            int needed = 0, bufSize;
            IntPtr buf;
            INTERNET_CACHE_ENTRY_INFO cacheItem;
            IntPtr hEnum;
            bool r;

            FindFirstUrlCacheEntry(null, IntPtr.Zero, ref needed);
            if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
                return;

            bufSize = needed;
            buf = Marshal.AllocHGlobal(bufSize);
            hEnum = FindFirstUrlCacheEntry(null, buf, ref needed);
            while (true)
            {
                cacheItem = (INTERNET_CACHE_ENTRY_INFO)Marshal.PtrToStructure(buf,
                 typeof(INTERNET_CACHE_ENTRY_INFO));

                DateTime modifiedTime = FILETIMEtoDataTime(cacheItem.LastModifiedTime);
                DateTime expireTime = FILETIMEtoDataTime(cacheItem.ExpireTime);
                DateTime accessTime = FILETIMEtoDataTime(cacheItem.LastAccessTime);
                DateTime syncTime = FILETIMEtoDataTime(cacheItem.LastSyncTime);

                string s;
                #region 获得数据
                try
                {
                    String url = Marshal.PtrToStringAuto(cacheItem.lpszSourceUrlName);
                    if (Regex.Match(url, @"http://n.douban.com/view/song/small/p\d+.mp3").Success)
                    {
                        s = Marshal.PtrToStringAuto(cacheItem.lpszSourceUrlName)
    + System.Environment.NewLine
    + "  ===> " + GetName(cacheItem.lpszLocalFileName)
    + System.Environment.NewLine
    + "  ==> " + Marshal.PtrToStringAuto(cacheItem.lpHeaderInfo)
    + System.Environment.NewLine
    + Marshal.PtrToStringAuto(cacheItem.lpszFileExtension);
                        Console.WriteLine(s);
                    }
                }
                catch
                {

                }
                #endregion
                needed = bufSize;
                r = FindNextUrlCacheEntry(hEnum, buf, ref needed);
                if (!r && Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
                    break;
                if (!r && needed > bufSize)
                {
                    bufSize = needed;
                    buf = Marshal.ReAllocHGlobal(buf, (IntPtr)bufSize);
                    FindNextUrlCacheEntry(hEnum, buf, ref needed);
                }
            }
            Marshal.FreeHGlobal(buf);
        }
        #endregion
    }
}
